package taskTracker;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import taskTracker.util.Serializable;

/**
 * A tag is a label that can be attached to any taggable object.
 * @author aseldawy
 *
 */
public class Tag implements Serializable {
	/**Holds all tags in the program*/
	private static Hashtable tags = new Hashtable(); 
	
	/**Holds all tags along with the order at which they were saved*/
	private static Hashtable tagsIndexes;
	
	/**Holds the tags in the order they were read*/
	private static Vector tagsVector;
	
	/** Name of this tag */
	private String name;
	
	/** Objects tagged with this tag */
	private Vector tagged;
	
	/**Delimiter for tabs*/
	public static final char TAG_DELIMITER = ',';
	
	/**
	 * Creates a new tag with the given name.
	 * @param name
	 */
	private Tag(String name) {
		this.name = name;
		this.tagged = new Vector();
		
		if(tags == null) {
			tags = new Hashtable();
		}
	}

	/**
	 * Retrieves name of this tag
	 * @return
	 */
	public String getName() {
		return name;
	}

	/**
	 * Return all objects tagged with this tag
	 * @return
	 */
	public Vector getTagged() {
		return tagged;
	}
	
	/**
	 * Add an object to this tag.
	 * This method should be kept in package visibility because it is called by TaggedObject only
	 * @param obj
	 */
	void addTaggedObject(Object obj) {
		if (!this.tagged.contains(obj)) {
			this.tagged.addElement(obj);
		}
	}

	/**
	 * Remove an object from this tag.
	 * This method should be kept in package visibility because it is called by TaggedObject only
	 * @param obj
	 */
	void removeTaggedObject(Object obj) {
		this.tagged.removeElement(obj);
		// remove this tag if it becomes empty
		if(this.tagged.isEmpty()) {
			tags.remove(this.name);
		}
	}
	
	/**
	 * Retrieve or create the tag with the given name (case sensitive)
	 * @param name
	 * @return
	 */
	public static Tag getTag(String name) {
		Tag tag = (Tag) tags.get(name);
		if (tag == null) {
			tags.put(name, tag = new Tag(name));
		}
		return tag;
	}

	public String toString() {
		return getName();
	}

	public boolean equals(Object obj) {
		return getName().equals(((Tag)obj).getName());
	}

	public int hashCode() {
		return getName().hashCode();
	}

	public Tag(DataInputStream dis) throws IOException {
		getTag(dis.readUTF());
	}

	public void serialize(DataOutputStream dos) {
		try {
			dos.writeUTF(name);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static Hashtable getTagsIndexes() {
		return tagsIndexes;
	}

	/**
	 * Used when reading tags from disk.
	 * This methods returns all tags in the order they were read from disk.
	 * @return
	 */
	public static Vector getTagsVector() {
		return tagsVector;
	}

	public static void serializeAllTags (DataOutputStream dos)
	{
		// Remove tags with 0 elements
		for (Enumeration e = tags.elements(); e.hasMoreElements();) {
			Tag tag = (Tag) e.nextElement();
			if (tag.getTagged().isEmpty()) {
				tags.remove(tag.getName());
			}
		}
		
		tagsIndexes = new Hashtable();
		
		Enumeration enumeration= tags.elements();
		int index=0; // the index at which the tag is saved
		try 
		{
			dos.writeInt(tags.size()); 
			while (enumeration.hasMoreElements())
			{
				Tag tag =(Tag)enumeration.nextElement();
				tag.serialize(dos);
				tagsIndexes.put(tag.getName(), new Integer(index));
				index ++;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
	
	public static void deserializeAllTags(DataInputStream dis)
	{
		tags = new Hashtable();
		tagsVector = new Vector();
		try {
			int tagsNumber = dis.readInt();
			for (int i=0 ;i<tagsNumber;i++)
			{
				String tagName= dis.readUTF();
				Tag tag=getTag(tagName);
				tagsVector.addElement(tag);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Returns an enumeration that iterates through all tags
	 * @return
	 */
	public static Enumeration getAllTags() {
		return tags.elements();
	}
}
